package cn.com.dcsgo.service.impl;

import cn.com.dcsgo.constants.CoreConstant;
import cn.com.dcsgo.domain.dto.SysMenuSaveDTO;
import cn.com.dcsgo.domain.dto.SysMenuUpdateDTO;
import cn.com.dcsgo.domain.po.SysMenu;
import cn.com.dcsgo.domain.vo.RouterVO;
import cn.com.dcsgo.domain.vo.SysMenuVO;
import cn.com.dcsgo.enums.DictEnums;
import cn.com.dcsgo.exception.BusinessException;
import cn.com.dcsgo.mapper.SysMenuMapper;
import cn.com.dcsgo.mapper.SysRoleMapper;
import cn.com.dcsgo.service.SysMenuService;
import cn.com.dcsgo.utils.ConverterUtils;
import cn.hutool.core.lang.Assert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * SysMenuServiceImpl
 *
 * @author Dcsgo
 * @since 28/5/2025 上午 8:38
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl implements SysMenuService {
    private final SysMenuMapper sysMenuMapper;
    private final SysRoleMapper sysRoleMapper;

    @Override
    public List<SysMenuVO> list() {
        List<SysMenu> sysMenuList;

        // 管理员直接查询所有菜单
        if (getCurrentUser().getUserType().equals(CoreConstant.USER_TYPE_ADMIN)) {
            sysMenuList = sysMenuMapper.selectAll();
        } else {
            // 其他用户需要根据用户、角色、菜单多表查询其菜单信息
            sysMenuList = sysMenuMapper.selectByUserId(getCurrentUser().getUserId());
        }
        log.debug("sysMenuList:{}", sysMenuList);

        // 没有菜单提前返回
        if (CollectionUtils.isEmpty(sysMenuList)) {
            return new ArrayList<>(0);
        }

        // 构造层级结构
        return buildLevelHelper(sysMenuList);
    }

    @Override
    public SysMenu get(Long menuId) {
        return sysMenuMapper.selectByPrimaryKey(menuId);
    }

    @Override
    public List<SysMenuVO> directoryAndMenuList() {
        List<SysMenu> menuList;
        // 管理员查询全部
        if (getCurrentUser().getUserType().equals(CoreConstant.USER_TYPE_ADMIN)) {
            menuList = sysMenuMapper.selectAllDirectoryAndMenu();
        } else {
            // 非管理员通过用户、角色、菜单联表查询拥有其权限的菜单和目录
            // menu_type 菜单类型（0目录 1菜单 2按钮）
            menuList = sysMenuMapper.selectAllDirectoryAndMenuByUserId(getCurrentUser().getUserId());
        }
        return buildLevelHelper(menuList);
    }

    @Override
    public int update(SysMenuUpdateDTO updateDTO) {
        SysMenu updateData = sysMenuMapper.selectByPrimaryKey(updateDTO.getMenuId());
        Assert.notNull(updateData, "更新的菜单编号" + updateDTO.getMenuId() + "不存在");
        ConverterUtils.syncFieldsWithNullOnEqual(updateDTO, updateData, "menuId");

        updateData.setUpdateBy(getUsername());
        updateData.setUpdateTime(new Date());

        // 不需要更新的字段置空(数据库中有而 updateDTO 中没有的)
        updateData.setCreateBy(null);
        updateData.setCreateTime(null);
        updateData.setParentId(null);
        updateData.setMenuType(null);

        return sysMenuMapper.updateByPrimaryKeySelective(updateData);
    }

    @Transactional(rollbackFor = {Throwable.class})
    @Override
    public int remove(Long menuId) {
        // 校验当前菜单是否有子菜单
        Assert.isFalse(sysMenuMapper.hasChildren(menuId), "当前菜单有子菜单，不允许删除");
        int row = sysMenuMapper.deleteByPrimaryKey(menuId);

        // 删除角色菜单关联表中的关联数据
        row += sysRoleMapper.deleteRoleMenuByMenuId(menuId);
        return row;
    }

    @Override
    public List<RouterVO> getRouters() {
        List<SysMenuVO> sysMenuVoS = this.directoryAndMenuList();
        // 将具有层级关系的目录和菜单信息转换成路由信息
        return getRouteOf(sysMenuVoS);
    }

    /**
     * 将具有层级关系的目录和菜单信息转换成路由信息
     *
     * @param sysMenuVoS 具有层级关系的目录和菜单信息
     * @return 具有层级关系的路由信息
     */
    private List<RouterVO> getRouteOf(List<SysMenuVO> sysMenuVoS) {
        List<RouterVO> result = new LinkedList<>();
        for (SysMenuVO menuVO : sysMenuVoS) {
            RouterVO route = new RouterVO();
            result.add(route);

            // 数据库没有带 斜线 所以名字自接用 path 了
            route.setName(menuVO.getPath());

            // 菜单类型为目录时 path component 需要特殊处理
            if (menuVO.getMenuType().equals(DictEnums.MENU_M.getCode())) {
                // 需要在路径上额外添加斜线
                menuVO.setPath(CoreConstant.URL_SPLIT + menuVO.getPath());
                // 组件为固定的 Layout
                menuVO.setComponent(CoreConstant.DEFAULT_COMPONENT);
            }

            // 设置路由中的其他属性
            route.setPath(menuVO.getPath());
            // 目录总是显示
            route.setAlwaysShow(menuVO.getMenuType().equals(DictEnums.MENU_M.getCode()));
            route.setHidden(menuVO.getVisible().equals(CoreConstant.HIDDEN_STATE));
            route.setComponent(menuVO.getComponent());

            // 设置元数据
            route.getMeta().setTitle(menuVO.getMenuName());
            route.getMeta().setIcon(menuVO.getIcon());

            // 递归设置子路由
            if (!CollectionUtils.isEmpty(menuVO.getChildren())) {
                route.setChildren(getRouteOf(menuVO.getChildren()));
            }
        }

        return result;
    }

    @Transactional(rollbackFor = {Throwable.class})
    @Override
    public int save(SysMenuSaveDTO saveDTO) {
        SysMenu sysMenu = new SysMenu();
        log.debug("saveDTO:{}", saveDTO);
        BeanUtils.copyProperties(saveDTO, sysMenu);
        log.debug("sysMenu:{}", sysMenu);

        sysMenu.setCreateBy(getUsername());
        sysMenu.setCreateTime(new Date());

        // 如果添加的菜单为目录则其父菜单id只能是默认的父级菜单编号
        if (sysMenu.getMenuType().equals(DictEnums.MENU_M.getCode())
                && !sysMenu.getParentId().equals(CoreConstant.DEFAULT_PARENT_ID)) {
            throw new BusinessException("菜单类型为目录时其上级菜单只能为主类目");
        }

        // 如果添加菜单的父菜单id为根菜单id则其类型只能为目录
        if (sysMenu.getParentId().equals(CoreConstant.DEFAULT_PARENT_ID)
                && !sysMenu.getMenuType().equals(DictEnums.MENU_M.getCode())) {
            throw new BusinessException("上级菜单为主类目时菜单类型为只能为目录");
        }

        // 如果添加的菜单为子菜单
        if (!sysMenu.getParentId().equals(CoreConstant.DEFAULT_PARENT_ID)) {
            // 校验其父菜单是否存在
            if (!sysMenuMapper.hasMenuId(sysMenu.getParentId())) {
                throw new BusinessException("添加的菜单其上级菜单(菜单编号为" + sysMenu.getParentId() + ")不存在");
            }
            // 校验其菜单类型是否合法 目录能加菜单，菜单能加按钮反之即为不合法
            SysMenu parent = sysMenuMapper.selectByPrimaryKey(sysMenu.getParentId());
            Integer directory = DictEnums.MENU_M.getCode();
            Integer menu = DictEnums.MENU_C.getCode();
            Integer button = DictEnums.MENU_F.getCode();
            Integer childType = sysMenu.getMenuType();
            Integer parentType = parent.getMenuType();
            if (parentType.equals(directory) && !childType.equals(menu)) {
                throw new BusinessException("菜单类型错误，上级菜单为目录时菜单类型只能为菜单");
            }
            if (parentType.equals(menu) && !childType.equals(button)) {
                throw new BusinessException("菜单类型错误，上级菜单为菜单时菜单类型只能为按钮");
            }
        }

        int row = 0;
        int maxTryCount = 10;
        // 通过自旋锁解决并发插入的问题
        while (maxTryCount > 0) {
            // 主键手动生成，主键结构：
            // 菜单类型为0(目录) 主键为 0~99 自增编号
            // 其他类型 父菜单id + xx，xx为自增编号

            //查询指定菜单类型和父菜单编号下最大的菜单id
            Long maxMenuId = sysMenuMapper.selectMaxMenuIdByMenuTypeAndParentId(sysMenu.getMenuType(), sysMenu.getParentId());
            if (maxMenuId == null) {
                // 当前数据是指定菜单类型和父菜单编号下的第一个子元素
                if (sysMenu.getMenuType().equals(DictEnums.MENU_M.getCode())) {
                    // 当前数据是目录，即为表中第一个添加的目录数据
                    sysMenu.setMenuId(1L);
                } else {
                    // 当前数据不是目录，是其父节点下第一个插入的菜单或按钮数据，id为父菜单id+01
                    sysMenu.setMenuId(Long.parseLong(sysMenu.getParentId() + "01"));
                }
            } else {
                // 当前数据有兄弟节点，id为其菜单类型和父菜单编号下最大的菜单id+1
                if ((maxMenuId + 1) % 100 == 0) {
                    log.error("菜单: {} 下的子菜单已达到极限即99个或编号已自增到99", sysMenu.getParentId());
                    throw new BusinessException("当前菜单下无法容纳更多子菜单");
                }
                sysMenu.setMenuId(maxMenuId + 1);
            }

            try {
                row = sysMenuMapper.insertSelective(sysMenu);
                break;
            } catch (DuplicateKeyException sqlException) {
                maxTryCount--;
                log.info("菜单编号{}重复，进行下一次尝试", sysMenu.getMenuId());
            }
        }

        return row;
    }

    /**
     * 通过菜单集合构建有层级关系的菜单集合
     *
     * @param sysMenuList 菜单集合
     * @return 有层级关系的菜单集合
     */
    private List<SysMenuVO> buildLevelHelper(List<SysMenu> sysMenuList) {
        // 将 sysMenuList 转换成 SysMenuVo 并按 menuId 分组
        Map<Long, SysMenuVO> sysMenuVoMap = new HashMap<>(sysMenuList.size());
        for (SysMenu m : sysMenuList) {
            SysMenuVO sysMenuVO = new SysMenuVO();
            BeanUtils.copyProperties(m, sysMenuVO);
            sysMenuVoMap.put(sysMenuVO.getMenuId(), sysMenuVO);
        }
        log.debug("sysMenuVOMap:{}", sysMenuVoMap);

        List<SysMenuVO> result = new LinkedList<>();
        // 使用 sysMenuList 进行遍历是因为数据库进行了排序的
        // sysMenuVoMap.values 中的元素是乱序的
        for (SysMenu m : sysMenuList) {
            // 当前菜单是一级菜单 直接添加
            if (m.getParentId().equals(CoreConstant.DEFAULT_PARENT_ID)) {
                result.add(sysMenuVoMap.get(m.getMenuId()));
            } else {
                // 不是一级菜单 添加到它的父菜单的子节点集合中
                SysMenuVO parent = sysMenuVoMap.get(m.getParentId());
                if (parent == null) {
                    log.error("数据库中数据有误!请查看菜单表中菜单id为:{}的菜单的父菜单(其菜单id为:{})是否存在", m.getMenuId(), m.getParentId());
                    throw new BusinessException("后台数据有误请联系管理员");
                }
                parent.getChildren().add(sysMenuVoMap.get(m.getMenuId()));
            }
        }
        return result;
    }
}
